﻿using Basic.MQTT.Contracts;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Packets;
using MQTTnet.Protocol;

namespace Basic.MQTT;

public class BasicMqttClient : IBasicMqttClient
{
    private readonly MqttFactory Factory = new MqttFactory();
    private IMqttClient Client;

    public MqttQualityOfServiceLevel QualityLevel { get; set; }
    public Func<MqttApplicationMessageReceivedEventArgs, Task> ReceivedEvent { get; set; }
    public string ClientId => Config?.ClientId;
    public bool IsConnected => Client?.IsConnected ?? false;
    public MqttClientConfig Config { get; set; }
    public BasicMqttClient() : this(null) { }

    public BasicMqttClient(MqttClientConfig? config)
    {
        Config = config;
    }

    private MqttClientOptions GetClientOptions(MqttClientConfig config)
    {
        if (string.IsNullOrEmpty(config?.Host)) throw new ArgumentNullException(nameof(MqttClientConfig.Host));
        if (config == null || config.Port == 0) throw new ArgumentNullException(nameof(MqttClientConfig.Port));
        if (string.IsNullOrEmpty(config?.ClientId)) throw new ArgumentNullException(nameof(MqttClientConfig.ClientId));

        var builder = new MqttClientOptionsBuilder()
            .WithTcpServer(config.Host, config.Port)
            .WithClientId(config.ClientId);

        if (!string.IsNullOrEmpty(config?.UserName) && !string.IsNullOrEmpty(config?.Passwd))
            builder.WithCredentials(config.UserName, config.Passwd);

        if (config?.KeepAlivePeriod > 0)
            builder.WithKeepAlivePeriod(TimeSpan.FromSeconds(config.KeepAlivePeriod));

        builder.WithCleanStart(config.CleanStart)
            .WithProtocolVersion(config.ProtocolVersion)
            .WithSessionExpiryInterval(config.SessionNeverExpiry ? uint.MaxValue : (config.SessionExpiryInterval ?? 0));
        return builder.Build();
    }

    public async Task<bool> ConnectAsync()
    {
        Client = Factory.CreateMqttClient();
        Client.ConnectedAsync += ClientConnected;
        Client.DisconnectedAsync += ClientDisconnected;
        if (ReceivedEvent != null) Client.ApplicationMessageReceivedAsync += ReceivedEvent;
        MqttClientOptions option = GetClientOptions(Config);
        MqttClientConnectResult result = await Client.ConnectAsync(option);
        return result.ResultCode == MqttClientConnectResultCode.Success;
    }

    public async Task DisconnectAsync()
    {
        if (Client != null)
        {
            Client.ConnectedAsync -= ClientConnected;
            Client.DisconnectedAsync -= ClientDisconnected;
            if (ReceivedEvent != null) Client.ApplicationMessageReceivedAsync -= ReceivedEvent;
            if (Client.IsConnected) await Client.DisconnectAsync();
            Client.Dispose();
            Client = null;
        }
    }

    public Task<MqttClientPublishResult> PublishAsync(string topic, byte[] payload, MqttQualityOfServiceLevel qualityLevel, bool retain = false)
    {
        return Client.PublishBinaryAsync(topic, payload, qualityLevel, retain);
    }

    public Task<MqttClientSubscribeResult> SubscribeAsync(List<string> topics, bool noLocal = true)
    {
        var topicFilters = topics.Select(s => new MqttTopicFilter { Topic = s, NoLocal = noLocal }).ToList();
        var subscribeOptions = new MqttClientSubscribeOptions { TopicFilters = topicFilters };
        return Client.SubscribeAsync(subscribeOptions);
    }

    public Task<MqttClientUnsubscribeResult> UnsubscribeAsync(List<string> topics) => Client.UnsubscribeAsync(new MqttClientUnsubscribeOptions { TopicFilters = topics });

    private Task ClientConnected(MqttClientConnectedEventArgs args)
    {
        Console.WriteLine($"MqttClient[{Config.Host}:{Config.Port}]; Connect Result:{args.ConnectResult.ResultCode}");
        return Task.CompletedTask;
    }

    private Task ClientDisconnected(MqttClientDisconnectedEventArgs args)
    {
        Console.WriteLine($"MqttClient[{Config.Host}:{Config.Port}]; Disconnected Result:{args.ConnectResult.ResultCode}");
        return Task.CompletedTask;
    }

    #region Dispose()

    private bool disposedValue;

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                DisconnectAsync().ConfigureAwait(false).GetAwaiter().GetResult();
            }
            // TODO: 释放未托管的资源(未托管的对象)并重写终结器
            // TODO: 将大型字段设置为 null
            disposedValue = true;
        }
    }

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }

    #endregion
}